package com.tianji.promotion.service.impl;

import  cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.discount.DiscountStrategy;
import com.tianji.promotion.domain.dto.UserCouponDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.UserCouponQuery;
import com.tianji.promotion.domain.utils.CodeUtil;
import com.tianji.promotion.domain.utils.MyLockStrategy;
import com.tianji.promotion.domain.utils.annotation.MyLock;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.ExchangeCodeStatus;
import com.tianji.promotion.enums.MyLockType;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户领取优惠券的记录，是真正使用的优惠券信息 服务实现类
 * </p>
 *
 * @author Yandel
 * @since 2025-08-17
 */
@Slf4j
@Service
public class UserCouponMqServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {

    @Resource
    private CouponMapper couponMapper;//不能直接注入couponService,避免循环依赖

    @Resource
    private IExchangeCodeService exchangeCodeService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RabbitMqHelper mqHelper;

    @Override
    @MyLock(name = "lock:coupon:uid:${couponId}")
//    @Transactional
    public void receiveCoupon(Long couponId) {
        if (couponId == null) {
            throw new BadRequestException("优惠券ID不能为空");
        }
//        Coupon coupon = couponMapper.selectById(couponId);
        //1、从Redis中获取优惠券信息
        Coupon coupon = queryCouponByCache(couponId);
        //校验优惠券是否存在，不存在无法领取
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }

        //2、校验优惠券的发放时间，是不是正在发放中
//        if (coupon.getStatus() != CouponStatus.ISSUING) {
//            throw new BadRequestException("优惠券状态错误");
//        }
        LocalDateTime now = LocalDateTime.now();
        log.info("当前时间为,{}", now);
        log.info("coupon信息为，{}", coupon);
        if (now.isBefore(coupon.getIssueBeginTime()) || now.isAfter(coupon.getIssueEndTime())) {
            throw new BadRequestException("优惠券已过期或者发放时间已过");
        }
        //3、校验优惠券剩余库存是否充足
//        if (coupon.getTotalNum() <= 0 || coupon.getIssueNum() >= coupon.getTotalNum()) {
//            throw new BadRequestException("优惠券库存不足");
//        }
        if (coupon.getTotalNum() <= 0) {
            throw new BadRequestException("优惠券库存不足");
        }

        //获取当前用户 对该优惠券 已领数量  user_coupon   统计数量
 /*       Long userId = UserContext.getUser();
        Integer count = this.lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getCouponId, couponId)
                .count();
        //校验优惠券的每人限领数量
        if (count != null && count >= coupon.getUserLimit()) {
            throw new BadRequestException("优惠券已领完");
        }

        //已发的优惠券+1
        couponMapper.incrIssueNum(coupon.getId());//采用这种方式，会存在并发问题，后期考虑用乐观锁解决

        //生成用户券
        saveUserCoupon(coupon, userId);*/ //提出公共函数
        Long userId = UserContext.getUser();

        //直接使用synchronized锁 - 解决并发问题
/*        synchronized (userId.toString().intern()){//细度锁，锁的范围缩小，只锁定当前用户
            checkAndCreateCoupon(userId, coupon,null);
        }*/
        //使用synchronized锁+AOP代理对象 -解决并发+事务问题
/*
        synchronized (userId.toString().intern()) {//细度锁，锁的范围缩小，只锁定当前用户
            IUserCouponService userCouponService = (IUserCouponService) AopContext.currentProxy();
//            checkAndCreateCoupon(userId, coupon,null);//该写法是调用原对象的方法【无事务调用有事务的】
            userCouponService.checkAndCreateCoupon(userId, coupon, null);//这种写法是调用代理对象的方法，方法是事务处理的
        }
*/

        //使用Redis分布式锁 + AOP代理对象 -解决并发+事务问题
       /* String key = "lock:coupon:uid" + userId;
        RedisLock redisLock = new RedisLock(key, stringRedisTemplate);
        try {
            boolean isLock = redisLock.tryLock(5, TimeUnit.SECONDS);
            if (!isLock) {//isLock-false
                throw new BizIllegalException("操作太频繁了~");
            }
            IUserCouponService userCouponService = (IUserCouponService) AopContext.currentProxy();
//            checkAndCreateCoupon(userId, coupon,null);//该写法是调用原对象的方法【无事务调用有事务的】
            userCouponService.checkAndCreateCoupon(userId, coupon, null);//这种写法是调用代理对象的方法，方法是事务处理的

        } finally {
            redisLock.unlock();
        }*/

        //使用Redission实现分布式锁
/*
        String key = "lock:coupon:uid" + userId;
        RLock lock = redissonClient.getLock(key);
        try {
            boolean isLock = lock.tryLock(1, 5, TimeUnit.SECONDS);
            if (!isLock){
                throw new BizIllegalException("操作太频繁了~");
            }
            IUserCouponService userCouponService = (IUserCouponService) AopContext.currentProxy();
            userCouponService.checkAndCreateCoupon(userId, coupon, null);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
*/


        //4、统计已领取数量
        //long num = redisTemplate.opsforHash.get(key, userid);
        String key = PromotionConstants.USER_CoUPON_CACHE_KEY_PREFIX + coupon.getId();//prs:User:coupon:优惠券id
        //修改已经数量 +1
        //increment代表本次领取后的    已领数量
        Long increment = stringRedisTemplate.opsForHash().increment(key, userId.toString(), 1);
        //校验是否超过领取限额数量
        if (increment > coupon.getUserLimit()) {
            throw new BizIllegalException("超出限领取数量");
        }

        //5、修改优惠券的库存 -1
        String CountKey = PromotionConstants.COUPON_CACHE_KEY_PREFIX + coupon.getId();
        stringRedisTemplate.opsForHash().increment(CountKey, "totalNum", -1);

        //6、发送消息到mq   消息内容为   userId     couponId
        UserCouponDTO msg = new UserCouponDTO();
        msg.setCouponId(coupon.getId());
        msg.setUserId(userId);
        mqHelper.send(MqConstants.Exchange.PROMOTION_EXCHANGE, MqConstants.Key.COUPON_RECEIVE, msg);

/*        IUserCouponService userCouponService = (IUserCouponService) AopContext.currentProxy();
        userCouponService.checkAndCreateCoupon(userId, coupon, null);*/
    }

    /**
     * 从Redis中获取优惠券信息（领取开始和结束时间、发行总数量、限领数量）
     *
     * @param
     * @return
     */
    public Coupon queryCouponByCache(Long id) {
        //1.拼接key
        String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + id;
        log.info("查询优惠券存在Redis中的key：{}", key);
        //2.从Redis中获取优惠券信息
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(key);
        log.info("redis中取到的数据：{}", entries);
        if (entries.isEmpty() || entries == null) {
            return null;
        }
        Coupon newCoupon = new Coupon();
//        Coupon coupon = BeanUtils.mapToBean(entries, Coupon.class, false, CopyOptions.create());
        newCoupon.setId(id);
        newCoupon.setTotalNum(Integer.valueOf(entries.get("totalNum").toString()));
        newCoupon.setUserLimit(Integer.valueOf(entries.get("userLimit").toString()));
        Object issueBeginTime = entries.get("issueBeginTime");
        Object issueEndTime = entries.get("issueEndTime");

        newCoupon.setIssueBeginTime(DateUtil.date(
                Long.parseLong(issueBeginTime.toString())//1、字符串转为long
        ).toLocalDateTime());//2、再使用hutool转换为LocalDateTime

        newCoupon.setIssueEndTime(DateUtil.date(
                Long.parseLong(issueEndTime.toString())//字符串转为long
        ).toLocalDateTime());

        log.info("coupon优惠券信息：{}", newCoupon);
        return newCoupon;

    }

    //这里直接使用注解表名锁的 类型、锁的等待时间、锁的持有时间
    @MyLock(name = "lock:coupon:uid:#{userId}",
            waitTime = 1,
            leaseTime = 5,
            lockType = MyLockType.RE_ENTRANT_LOCK,
            lockStrategy = MyLockStrategy.FAIL_AFTER_RETRY_TIMEOUT)
    @Transactional
    public void checkAndCreateCoupon(Long userId, Coupon coupon, Long serialNum) {
        //Long类型-128~127之间是同一个对象     超过该区间则是不同的对象
        //Long.toString方法底层是newString，所以还是不同的对象
        //Long.toString.intern（）intern方法是强制从常量池中取字符串
/*        synchronized (userId.toString().intern()){//用户限领数量判断是针对单个用户的，因此锁的范围不需要是整个方法，只要锁定某个用户即可
            //1/获取当前用户 对该优惠券 已领数量  user_coupon   统计数量
            Integer count = this.lambdaQuery()
                    .eq(UserCoupon::getUserId, userId)
                    .eq(UserCoupon::getCouponId, coupon.getId())
                    .count();
            //2、校验优惠券的每人限领数量
            if (count != null && count >= coupon.getUserLimit()) {
                throw new BadRequestException("优惠券已领完");
            }
            //已发的优惠券+1
            couponMapper.incrIssueNum(coupon.getId());//采用这种方式
            //3、生成用户券
            saveUserCoupon(coupon, userId);

            //4、更新兑换码的状态
            if (serialNum != null) {
                exchangeCodeService.lambdaUpdate()
                        .set(ExchangeCode::getUserId, userId)
                        .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED)
                        .eq(ExchangeCode::getId, serialNum)
                        .update();
            }
        }*/
        //1/获取当前用户 对该优惠券 已领数量  user_coupon   统计数量
        Integer count = this.lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getCouponId, coupon.getId())
                .count();
        //2、校验优惠券的每人限领数量
        if (count != null && count >= coupon.getUserLimit()) {
            throw new BadRequestException("优惠券已领完");
        }
        //已发的优惠券+1
        couponMapper.incrIssueNum(coupon.getId());//采用这种方式
        //3、生成用户券
        saveUserCoupon(coupon, userId);

        //4、更新兑换码的状态
        if (serialNum != null) {
            exchangeCodeService.lambdaUpdate()
                    .set(ExchangeCode::getUserId, userId)
                    .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED)
                    .eq(ExchangeCode::getId, serialNum)
                    .update();
        }
//        throw new RuntimeException("测试异常");
    }

    @Override
    @Transactional
    public void exchangeCoupon(String code) {
        //1、校验并解析兑换码
        if (code == null) {
            throw new BadRequestException("请输入兑换码");
        }
        //2、解析兑换码得到自增Id
        long serialNum = CodeUtil.parseCode(code);//自增id 这是使用自增id的生成的兑换码

        log.info("自增id：{}", serialNum);
        //3、判断兑换码是否已兑换 采用 Redis的bitmap setbit   key offset 1
        Boolean result = exchangeCodeService.updateExchaingeCodeMark(serialNum, true);
        if (result) {
            //说明兑换码已经被兑换了
            throw new BizIllegalException("兑换码已兑换");
        }
        try {//为什么要使用try catch   因为防止现成的兑换码因执行下面的代码出现异常后影响兑换码的作用
            //4.判断兑换码是否存在根据自增id查询主键查询
            ExchangeCode exchangeCode = exchangeCodeService.getById(serialNum);
            if (exchangeCode == null) {
                throw new BizIllegalException("兑换码不存在~");
            }
            //5.判断是否过期
            LocalDateTime now = LocalDateTime.now();
            LocalDateTime expiredTime = exchangeCode.getExpiredTime();
            if (now.isAfter(expiredTime)) {
                throw new BizIllegalException("兑换码已过期~");
            }
            //校验并生成优惠券
            Long userId = UserContext.getUser();
            //查询优惠券信息
            Coupon coupon = couponMapper.selectById(exchangeCode.getExchangeTargetId());
            if (coupon == null) {
                throw new BizIllegalException("优惠券不存在~");
            }
            //校验并生成优惠券
            checkAndCreateCoupon(userId, coupon, serialNum);
        } catch (Exception e) {
            //10.将兑换码的状态重置
            exchangeCodeService.updateExchaingeCodeMark(serialNum, false);
            throw e;
        }
    }


    @Override
    public PageDTO<CouponVO> queryUserCoupons(UserCouponQuery query) {
        Long userId = UserContext.getUser();
        Page<UserCoupon> page = this.lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getStatus, query.getStatus())
                .page(query.toMpPage());//执行分页查询
        List<UserCoupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        //查询所有的优惠券ids(set去重复元素)
        Set<Long> ids = records.stream().map(UserCoupon::getCouponId).collect(Collectors.toSet());
        //查询所有的优惠券
        List<Coupon> coupons = couponMapper.selectBatchIds(ids);
        //po 转 vo
        List<CouponVO> voList = BeanUtils.copyList(coupons, CouponVO.class);
        //返回pageDTO
        return PageDTO.of(page, voList);
    }


    //    @MyLock(name = "lock:coupon:uid:#{userId}",
//            waitTime = 1,
//            leaseTime = 5,
//            lockType = MyLockType.RE_ENTRANT_LOCK,
//            lockStrategy = MyLockStrategy.FAIL_AFTER_RETRY_TIMEOUT)
    @Transactional
    @Override
    public void checkAndCreateCouponNew(UserCouponDTO msg) {
        //1/获取当前用户 对该优惠券 已领数量  user_coupon   统计数量
//        Integer count = this.lambdaQuery()
//                .eq(UserCoupon::getUserId, userId)
//                .eq(UserCoupon::getCouponId, coupon.getId())
//                .count();
//        //2、校验优惠券的每人限领数量
//        if (count != null && count >= coupon.getUserLimit()) {
//            throw new BadRequestException("优惠券已领完");
//        }
        //1.从DB中查询优惠券
        Coupon coupon = couponMapper.selectById(msg.getCouponId());
        if (coupon == null) {
            return;
        }

        //已发的优惠券+1
        couponMapper.incrIssueNum(coupon.getId());//采用这种方式
        //3、生成用户券
        saveUserCoupon(coupon, msg.getUserId());
//        throw new RuntimeException("测试异常");


    }

    /**
     * 生成用户券
     *
     * @param coupon
     * @param userId
     */
    private void saveUserCoupon(Coupon coupon, Long userId) {
        // 1.基本信息
        UserCoupon uc = new UserCoupon();
        uc.setUserId(userId);
        uc.setCouponId(coupon.getId());

        // 2.有效期信息
        LocalDateTime termBeginTime = coupon.getTermBeginTime();// 优惠券有效期开始时间
        LocalDateTime termEndTime = coupon.getTermEndTime();// 优惠券有效期结束时间
        if (termBeginTime == null && termEndTime == null) {
            termBeginTime = LocalDateTime.now();
            termEndTime = termBeginTime.plusDays(coupon.getTermDays());//表示当前时间加优惠券有效日期
        }
        uc.setTermBeginTime(termBeginTime);
        uc.setTermEndTime(termEndTime);

        // 3.保存
        this.save(uc);
    }


    @Override
    public List<String> queryDiscountRules(List<Long> userCouponIds) {
        // 1.查询优惠券信息
        List<Coupon> coupons = baseMapper.queryCouponByUserCouponIds(userCouponIds, UserCouponStatus.USED);
        if (CollUtils.isEmpty(coupons)) {
            return CollUtils.emptyList();
        }

        // 2.转换规则
        return coupons.stream()
            .map(c -> DiscountStrategy.getDiscount(c.getDiscountType()).getRule(c))
            .collect(Collectors.toList());
    }
}
